package com.starmark.gateway.springcloud.provider.filter;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.starmark.gateway.springcloud.provider.constants.GatewayConstant;
import com.starmark.gateway.springcloud.provider.dto.AccessLogDto;
import com.starmark.gateway.springcloud.provider.service.ILogService;
import com.starmark.gateway.springcloud.provider.utils.RequestHelper;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Objects;

/**
 * 日志记录该功能
 *
 * @author starmark
 * @date 20-2-1  下午9:54
 */
@Component
@Slf4j
public class AccessLogFilter implements GlobalFilter, Ordered {


    @Autowired
    private ILogService logService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {


        ServerHttpRequest request = exchange.getRequest();

        AccessLogDto accessLogDto = this.buildAccessLogDto(exchange, request);
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        DataBufferUtils.release(join);
                        MediaType mediaType = exchange.getResponse().getHeaders().getContentType();
                        //返回是json,txt,xml 才记录返回值
                        if (Objects.requireNonNull(mediaType).getSubtype().contains("json") || Objects.requireNonNull(mediaType).getSubtype().contains("xml") || Objects.requireNonNull(mediaType).getSubtype().contains("text")) {
                            String responseData = new String(content, StandardCharsets.UTF_8);
                            if (log.isDebugEnabled()) {
                                log.debug("响应转前:{}", responseData);
                            }

                            if (log.isDebugEnabled()) {
                                log.debug("响应转后:{}", responseData);
                            }
                            byte[] uppedContent = responseData.getBytes(StandardCharsets.UTF_8);
                            accessLogDto.setResponseResult(responseData);
                            handleResponse(accessLogDto,exchange,Objects.requireNonNull(getStatusCode()).value());

                            return bufferFactory.wrap(uppedContent);
                        } else {

                            handleResponse(accessLogDto,exchange,Objects.requireNonNull(getStatusCode()).value());
                            return bufferFactory.wrap(content);
                        }

                    }));

                }
                // if body is not a flux. never got there.
                return super.writeWith(body);

            }
        };
        // replace response with+ decorator
        return chain.filter(exchange.mutate().response(decoratedResponse).build()).onErrorResume(e -> {
            log.error("请求异常", e);
            accessLogDto.setEndTime(Instant.now().toEpochMilli());
            accessLogDto.setResponseResult(ExceptionUtil.getMessage(e));
            accessLogDto.setCreateBy(RequestHelper.getUserId(exchange));
            accessLogDto.setProjectCode(RequestHelper.getRequestProjectCode(request));
            accessLogDto.setRedirectUrl(exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR).toString());

            exchange.getAttributes().remove(GatewayConstant.X_CLIENT_TOKEN_USER_ID);
            exchange.getAttributes().remove(GatewayConstant.X_CLIENT_PROJECT_CODE);
            logService.saveLog(accessLogDto);
            return Mono.error(e);
        });
    }


    private void handleResponse(AccessLogDto accessLogDto,ServerWebExchange exchange,int responseStatus){
        accessLogDto.setResponseStatus(responseStatus);
        accessLogDto.setEndTime(Instant.now().toEpochMilli());
        accessLogDto.setCreateBy(RequestHelper.getUserId(exchange));
        accessLogDto.setProjectCode(RequestHelper.getRequestProjectCode(exchange.getRequest()));
        accessLogDto.setRedirectUrl(exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR).toString());
        logService.saveLog(accessLogDto);
        exchange.getAttributes().remove(GatewayConstant.X_CLIENT_TOKEN_USER_ID);
        exchange.getAttributes().remove(GatewayConstant.X_CLIENT_PROJECT_CODE);
        exchange.getAttributes().remove("cachedRequestBodyStr");
    }

    private String getRequestParams(ServerWebExchange exchange, ServerHttpRequest request) {
        String requestParams;
        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
        if ("POST".equalsIgnoreCase(request.getMethodValue()) && !Objects.requireNonNull(mediaType).includes(MediaType.MULTIPART_FORM_DATA)) {
            requestParams = exchange.getAttributeOrDefault("cachedRequestBodyStr", "");

        } else {
            requestParams = request.getQueryParams().toString();
        }
        return requestParams;

    }

    /**
     * 构造日志对象
     *
     * @param exchange 数据交换
     * @param request  请求
     * @return 日志对象
     */
    private AccessLogDto buildAccessLogDto(ServerWebExchange exchange, ServerHttpRequest request) {
        AccessLogDto accessLogDto = new AccessLogDto();
        accessLogDto.setStartTime(Instant.now().toEpochMilli());
        accessLogDto.setRequestHeaders(request.getHeaders().toString());
        accessLogDto.setUrl(request.getURI().toString());
        accessLogDto.setRequestMethod(request.getMethodValue());
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        accessLogDto.setClientIp(remoteAddress.getAddress() + ":" + remoteAddress.getPort());
        accessLogDto.setRequestParams(this.getRequestParams(exchange, request));
        return accessLogDto;
    }

    @Override
    public int getOrder() {
        return -20;
    }


}
